package org.intellij.plugins.junit4;

import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.util.IncorrectOperationException;
import org.apache.log4j.Logger;

/**
 * Represents a method in a Java class.
 */
public class JavaMethod
{
    private enum Annotation
    {
        TEST("org.junit.Test"), BEFORE("org.junit.Before"), AFTER("org.junit.After");

        static Annotation find(String name)
        {
            if (name == null) return null;
            for (Annotation annotation : Annotation.values())
            {
                if (annotation.name.equals(name)) return annotation;
            }
            return null;
        }

        private final String name;

        private Annotation(String name) { this.name = name; }

        public String tag() { return "@" + name; }
    }


    private final Logger log = Logger.getLogger("CONSOLE-WARN");
    private final PsiMethod psiMethod;


    public JavaMethod(PsiMethod psiMethod)
    {
        this.psiMethod = psiMethod;
    }

    public JavaFile getJavaFile()
    {
        Project p = psiMethod.getProject();
        JavaFileFactory fac = JUnitProjectComponent.getProjectComponent(p).fileFactory();
        PsiClass parent = psiMethod.getContainingClass();
        return fac.create(parent);
    }

    public boolean isTest()
    {
        JavaFile file = getJavaFile();
        if (file == null) throw new RuntimeException("Null file for: " + psiMethod);
        return file.isTest();
    }

    public boolean isInnerClassMember()
    {
        PsiClass psiClass = psiMethod.getContainingClass();
        return isInnerClass(psiClass);
    }

    private boolean isInnerClass(PsiClass psiClass)
    {
        final String name = psiClass.getName();
        if (name == null) return false; // wish i could remember why...

        final PsiFile file = psiClass.getContainingFile();
        final String fileName = file.getName();
        return (fileName.indexOf(name) < 0);
    }


    public boolean hasTestAnnotation()
    {
        return hasAnnotation(Annotation.TEST);
    }

    public boolean hasAnnotation(Annotation annotation)
    {
        PsiModifierList mods = psiMethod.getModifierList();
        PsiAnnotation[] existingAnnotations = mods.getAnnotations();

        for (PsiAnnotation existing : existingAnnotations)
        {
            String qname = existing.getQualifiedName();
            Annotation found = Annotation.find(qname);
            if (found == null) continue;
            if (found == annotation) return true;
            // mods.findAnnotation()
        }

        return false;
    }


    public void removeTestAnnotation()
    {
        if (!isTest()) return;
        if (!hasTestAnnotation()) return;
        removeAnnotation(Annotation.TEST);
    }

    /**
     * Add TEST annotation to method.
     */
    public void annotateTest()
    {
        if (!isTest()) return;
        if (isInnerClassMember()) return;
        if (hasTestAnnotation()) return;
        addAnnotation(Annotation.TEST);
    }

    public void annotateTestExpectException(String exception)
    {
        if (!isTest()) return;
        if (hasTestAnnotation()) return;

    }


    public void toggleTestAnnotation()
    {
        if (!isTest()) return;
        if (hasTestAnnotation())
        {
            removeAnnotation(Annotation.TEST);
        }
        else
        {
            annotateTest();
        }

    }

    private void addAnnotation(final Annotation annotation)
    {
        // todo test to see if annotation exists first
        final Project p = psiMethod.getProject();

        Runnable task = new Runnable()
        {
            public void run()
            {
                try
                {
                    final PsiAnnotation psiAnnot = createPsiAnnotaton(annotation);
                    PsiModifierList mods = psiMethod.getModifierList();
                    PsiElement element = mods.addAfter(psiAnnot, null);
                    CodeStyleManager.getInstance(p).shortenClassReferences(element);

                    System.out.println(
                            "attrs=" + psiAnnot.getParameterList().getAttributes());
                }
                catch (IncorrectOperationException e)
                {
                    throw new RuntimeException(annotation + " failed on " + psiMethod, e);
                }
            }
        };
        WriteExecutor.run(p, "adding annotation:" + annotation, task);
    }

    private void removeAnnotation(final Annotation searchFor)
    {
        final Project p = psiMethod.getProject();

        Runnable task = new Runnable()
        {
            public void run()
            {
                try
                {
                    PsiModifierList mods = psiMethod.getModifierList();
                    PsiAnnotation[] existingAnnotations = mods.getAnnotations();

                    for (PsiAnnotation existing : existingAnnotations)
                    {
                        String qname = existing.getQualifiedName();
                        Annotation found = Annotation.find(qname);
                        if (found == null) continue;
                        if (found == searchFor)
                        {
                            existing.delete();
                        }
                    }
                }
                catch (IncorrectOperationException e)
                {
                    throw new RuntimeException(searchFor + " failed on " + psiMethod, e);
                }
            }
        };
        WriteExecutor.run(p, "adding searchFor:" + searchFor, task);
    }


    private PsiAnnotation createPsiAnnotaton(Annotation annotation)
            throws IncorrectOperationException
    {
        PsiManager manager = psiMethod.getManager();
        PsiElementFactory factory = manager.getElementFactory();
        return factory.createAnnotationFromText(annotation.tag(), psiMethod);
    }

    public String toString()
    {
        return "JavaMethod{" +
                "psiMethod=" + psiMethod +
                '}';
    }
}
